//-----------------------------------------------------------------------------
// ShUntypedUtilities
//
// Copyright (c) MetaSwitch 2006
//
// This file provides utilities to those example applications that use the
// "untyped" WSDL file and therefore deal with user data as XML.
//-----------------------------------------------------------------------------

import java.util.*;

import javax.xml.namespace.*;
import javax.xml.xpath.*;

import org.apache.axis.message.MessageElement;
import org.w3c.dom.*;

import com.MetaSwitch.EMS.SOAP.TUserData;
import com.MetaSwitch.EMS.SOAP.TSubResult;

public class ShUntypedUtilities extends ShUtilities
{
  //---------------------------------------------------------------------------
  // Define QNames identifying elements within a user data XML document.
  //---------------------------------------------------------------------------
  static final String USER_DATA_NAMESPACE_URI =
                              "http://www.metaswitch.com/ems/soap/sh/userdata";
  static final String SERVICE_DATA_NAMESPACE_URI =
                           "http://www.metaswitch.com/ems/soap/sh/servicedata";

  static final QName REPOSITORY_DATA = new QName(USER_DATA_NAMESPACE_URI,
                                                 "RepositoryData");
  static final QName SEQUENCE_NUMBER = new QName(USER_DATA_NAMESPACE_URI,
                                                 "SequenceNumber");
  static final QName SERVICE_DATA = new QName(USER_DATA_NAMESPACE_URI,
                                              "ServiceData");
  static final QName METASWITCH_DATA = new QName(SERVICE_DATA_NAMESPACE_URI,
                                                 "MetaSwitchData");
  static final QName VALUE = new QName(SERVICE_DATA_NAMESPACE_URI,
                                       "Value");
  static final QName SUBSCRIBED = new QName(SERVICE_DATA_NAMESPACE_URI,
                                            "Subscribed");
  static final QName USE_DEFAULT = new QName(SERVICE_DATA_NAMESPACE_URI,
                                             "UseDefault");

  //---------------------------------------------------------------------------
  // Set up an object to handle XPath queries.
  //---------------------------------------------------------------------------
  public final XPath xPath;

  public ShUntypedUtilities()
  {
    xPath = XPathFactory.newInstance().newXPath();
    NamespaceContext namespaceContext = new MetaSwitchNamespaceContext();
    xPath.setNamespaceContext(namespaceContext);
  }

  /**
   * Print to the console a representation of each field in user data.
   *
   * @param userData    A set of user data such as that returned on an Sh-Pull
   *                    response.
   */
  public void displayFields(TUserData userData)
  {
    StringBuilder displayFieldsBuilder = new StringBuilder();

    //-------------------------------------------------------------------------
    // Go down to the field group elements contained within the user data.
    //-------------------------------------------------------------------------
    MessageElement[] fieldGroupElements = getFieldGroupElements(userData);

    //-------------------------------------------------------------------------
    // Go through each field group and display its contents.
    //-------------------------------------------------------------------------
    for (MessageElement eachFieldGroup : fieldGroupElements)
    {
      displayFieldsBuilder.append("\nService indication " +
                                  eachFieldGroup.getName() + "\n\n");

      //-----------------------------------------------------------------------
      // Go through each field under this element and display its name and
      // value.
      //-----------------------------------------------------------------------
      Iterator<MessageElement> iterChildren =
                                             eachFieldGroup.getChildElements();
      while (iterChildren.hasNext())
      {
        MessageElement eachField = iterChildren.next();
        displayFieldsBuilder.append(eachField.getName() + ": " +
                                    getValue(eachField) + "\n");
      }
    }

    System.out.println(displayFieldsBuilder.toString());
  }

  /**
   * Extract the field group elements from user data.
   *
   * @returns           The field group element contained in the xml.
   *
   * @param userData    A set of user data such as that returned on an Sh-Pull
   *                    response.
   */
  public MessageElement[] getFieldGroupElements(TUserData userData)
  {
    //-------------------------------------------------------------------------
    // Fetch all the RepositoryData elements.
    //-------------------------------------------------------------------------
    Iterator<MessageElement> repositoryDatas = userData.get_any()[0]
                                            .getChildElements(REPOSITORY_DATA);

    List<MessageElement> fieldGroupList = new ArrayList<MessageElement>();

    while (repositoryDatas.hasNext())
    {
      MessageElement eachRepData = repositoryDatas.next();

      //-----------------------------------------------------------------------
      // Get all the field group elements and add them to the list.
      //-----------------------------------------------------------------------
      Object fieldGroupElement = eachRepData.getChildElement(SERVICE_DATA)
                                .getChildElement(METASWITCH_DATA)
                                .getChildren()
                                .get(0);
      fieldGroupList.add((MessageElement)fieldGroupElement);
    }

    return fieldGroupList.toArray(new MessageElement[]{});
  }

  /**
   * Extract the first field group element from user data.
   *
   * @returns           The field group element.
   *
   * @param userData    A set of user data such as that returned on an Sh-Pull
   *                    response.
   */
  public MessageElement getFieldGroupElement(TUserData userData)
  {
    //-------------------------------------------------------------------------
    // Get all the field group elements.
    //-------------------------------------------------------------------------
    MessageElement[] allFieldGroupElements = getFieldGroupElements(userData);

    //-------------------------------------------------------------------------
    // Return the first of them.
    //-------------------------------------------------------------------------
    return allFieldGroupElements[0];
  }

  /**
   * Create a string representation of the value an XML element.
   *
   * @returns           The value as a string. Where the element has child
   *                    elements, these are returned as name-value pairs.
   *
   * @param element     An XML element whose value to print.
   */
  public String getValue(MessageElement element)
  {
    String getValue;

    String value = element.getValue();
    if (value != null)
    {
      //-----------------------------------------------------------------------
      // This element has a text value - just return that.
      //-----------------------------------------------------------------------
      getValue = value;
    }
    else
    {
      //-----------------------------------------------------------------------
      // This element has no text value, so we'll display child elements.
      //-----------------------------------------------------------------------
      StringBuilder getValueBuilder = new StringBuilder();

      //-----------------------------------------------------------------------
      // If there is an element called "Value", display that first, and exclude
      // the word "Value".  This improves the presentation of switchable
      // default fields.
      //-----------------------------------------------------------------------
      MessageElement childValue = element.getChildElement(VALUE);
      if (childValue != null)
      {
        getValueBuilder.append(getValue(childValue) + " ");
      }

      //-----------------------------------------------------------------------
      // Go through each child element.  Display a list of their names and
      // values.
      //-----------------------------------------------------------------------
      Iterator iterChildren = element.getChildElements();
      if (iterChildren.hasNext())
      {
        getValueBuilder.append("{");

        while (iterChildren.hasNext())
        {
          MessageElement currentChild = (MessageElement)iterChildren.next();
          if (currentChild != childValue)
          {
            getValueBuilder.append(currentChild.getName() + "=" +
                                   getValue(currentChild) + ", ");
          }
        }

        //---------------------------------------------------------------------
        // Knock off the last comma and space.
        //---------------------------------------------------------------------
        getValueBuilder.setLength(getValueBuilder.length() - 2);
        getValueBuilder.append("}");
      }

      getValue = getValueBuilder.toString();
    }

    return getValue;
  }

  /**
   * Finds the current sequence number within the user data and increments it,
   * wrapping if necessary, so that the server accepts the change.
   *
   * @param userData    IN/OUT The user data whose sequence number to update.
   */
  public void incrementSequenceNumber(TUserData userData)
  {
    //-------------------------------------------------------------------------
    // Dig down to the "SequenceNumber" element and extract its current value.
    //-------------------------------------------------------------------------
    MessageElement shData = userData.get_any()[0];
    MessageElement repositoryData =
                    shData.getChildElement(ShUntypedUtilities.REPOSITORY_DATA);
    MessageElement sequenceNumber =
            repositoryData.getChildElement(ShUntypedUtilities.SEQUENCE_NUMBER);

    int value = Integer.parseInt(sequenceNumber.getValue());

    //-------------------------------------------------------------------------
    // Increment the value, wrapping back to 1 if it goes past the limit of
    // 65535.
    //-------------------------------------------------------------------------
    int newValue = value + 1;

    if (newValue > 65535)
    {
      newValue = 1;
    }
    sequenceNumber.setValue(String.valueOf(newValue));
  }

  /**
   * Get the name of the item identified by the SubResultSource in a particular
   * SubResult.  Overrides the implementation in ShUtilities.
   *
   * @returns           The name of the source item that caused a problem.
   *
   * @param subResult   The result containing the source string.
   * @param userData    The user data that was sent in.
   */
  public String getSourceItem(TSubResult subResult, TUserData userData)
  {
    //-------------------------------------------------------------------------
    // This overriding method tries to apply the sub-result's source as an
    // XPath query, resorting to string manipulation only if that doesn't work.
    //-------------------------------------------------------------------------
    String itemName = null;
    String source = subResult.getSubResultSource();

    if ((userData != null) && (source != null) && (source.length() > 0))
    {
      try
      {
        Document doc = userData.get_any()[0].getAsDocument();
        NodeList nodes =
                 (NodeList)xPath.evaluate(source, doc, XPathConstants.NODESET);

        if (nodes.getLength() > 0)
        {
          //-------------------------------------------------------------------
          // Having resolved the source to a part of the user data we sent in,
          // we'll just take the name of the element we found.  Normally an
          // application might do more than this, such as highlight the element
          // somehow in a user interface, but this serves as a demonstration.
          //-------------------------------------------------------------------
          itemName = nodes.item(0).getNodeName();
        }
      }
      catch (Exception e)
      {
        //---------------------------------------------------------------------
        // Nothing we can do about MessageElement-to-document conversion
        // errors.
        //---------------------------------------------------------------------
      }
    }

    if (itemName == null)
    {
      //-----------------------------------------------------------------------
      // Either we don't have any source info, or we don't have any user data
      // (e.g. this was an Sh-Pull operation), or we couldn't resolve the
      // source to something within the user data.  We'll resort to working
      // with the source as a string instead.
      //-----------------------------------------------------------------------
      itemName = super.getSourceItem(subResult, userData);
    }

    return itemName;
  }

  //---------------------------------------------------------------------------
  // This inner class is used by XPath to resolve namespace prefixes to the
  // full namespace URIs.
  //---------------------------------------------------------------------------
  class MetaSwitchNamespaceContext implements NamespaceContext
  {
    /**
     * Return the namespace URI for the specified prefix.
     *
     * @returns           The user data namespace URI for prefix u, otherwise
     *                    the service data namespace URI.
     *
     * @param prefix      The prefix to check.
     */
    public String getNamespaceURI(String prefix)
    {
      if (prefix.equals("u"))
      {
        return USER_DATA_NAMESPACE_URI;
      }
      else
      {
        return SERVICE_DATA_NAMESPACE_URI;
      }
    }

    /**
     * Returns the prefix for the specified namespace URI.
     *
     * @returns           u for user data, s otherwise.
     *
     * @param namespace   The namespace to check.
     */
    public String getPrefix(String namespace)
    {
      if (namespace.equals(USER_DATA_NAMESPACE_URI))
      {
        return "u";
      }
      else
      {
        return "s";
      }
    }

    /**
     * We have to implement this method, but it isn't required for any of our
     * uses of XPath, so it does nothing.
     *
     * @returns           null.
     *
     * @param namespace   The namespace to check.
     */
    public Iterator getPrefixes(String namespace)
    {
      return null;
    }
  }
}
